﻿using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using Sustainalytics.Entities.ProductInvolvement;
using Sustainalytics.Utils;
using Foxit.PDF;
using Foxit.PDF.PageElements;
using Foxit.PDF.Imaging;
using System.Reflection;

namespace Sustainalytics.ProductInvolvement.BL
{
    public class ProductInvolvementPDFReportLogic : IProductInvolvementPDFReportLogic
    {
        ProductInvolvementSerilog _log;
        
        private const float LeftMargin = 10;
        private const float PageHeight = 770;
        public bool ShowLayoutGrid { get; set; }

        private int _pageCounter;

        private Document _document;
        private Outline _parentOutline;
        private Outline _companyOutline;

        private readonly List<Page> _productsPages = new List<Page>();
        private readonly List<Page> _sourcesPages = new List<Page>();

        private static readonly Font CalibriBoldItalic = ReportGeneratorFramework.Utils.GetSystemFont("Calibri Bold Italic");
        private static readonly Font CalibriItalic = ReportGeneratorFramework.Utils.GetSystemFont("Calibri Italic");
        private static readonly Font CalibriBold = ReportGeneratorFramework.Utils.GetSystemFont("Calibri Bold");
        private static readonly Font CalibriRegular = ReportGeneratorFramework.Utils.GetSystemFont("Calibri");
        private static readonly Font CalibriLight = ReportGeneratorFramework.Utils.GetSystemFont("Calibri Light");
        private static readonly Font CambriaBold = ReportGeneratorFramework.Utils.GetSystemFont("Cambria Bold");

        private static readonly FontFamily CalibriFontFamily = new FontFamily("Calibri", CalibriRegular, CalibriBold, CalibriItalic, CalibriBoldItalic);

        private readonly RgbColor _barBackgroundColor = new RgbColor(189, 190, 192);

        //report images
        private static byte[] _companyLogoHeaderImage;
        private static byte[] _companyLogoFooterImage;

        public static string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return System.IO.Path.GetDirectoryName(path);
            }
        }


        public ProductInvolvementPDFReportLogic()
        {

        }

        public ProductInvolvementPDFReportLogic(ProductInvolvementSerilog log)
        {
            _log = log;
        }


        public byte[] Generate_PI_PDF_Report(ProductInvolvementData ca, IEnumerable<ProductInvolvementFramework> piIndicators, List<string> userClaimedIndicators)
        {
            var caDto = ProductInvolvementDataDto.FromCompanyAssessment(ca, piIndicators);

            // Create a _document and set it's properties
            _document = new Document
            {
                Creator = "Sustainalytics",
                Author = "Sustainalytics",
                Title = "Product Involvement Report"
            };

            _pageCounter = 0;

            // Add the top level outline to the _document
            _parentOutline = _document.Outlines.Add("Content");
            _parentOutline.Color = new WebColor("silver");
            _parentOutline.Style = TextStyle.Bold;

            _companyOutline = _parentOutline.ChildOutlines.Add("Company Information", new XYDestination(1, 0, 0));


            LoadStaticImages();

            AddProducts(caDto, piIndicators, userClaimedIndicators);
            AddSources(caDto);  

            var productsPageNumber = _pageCounter + 1;
            _productsPages.ForEach(p =>
            {
                _document.Pages.Add(p);
                _pageCounter++;
            });

            if (_productsPages.Any())
                _parentOutline.ChildOutlines.Add("Product Involvement", new XYDestination(productsPageNumber, 0, 0));

            var sourcesPageNumber = _pageCounter + 1;

            _sourcesPages.ForEach(p =>
            {
                _document.Pages.Add(p);
                _pageCounter++;
            });

            _parentOutline.ChildOutlines.Add("Sources", new XYDestination(sourcesPageNumber, 0, 0));

            var index = 1;
            foreach (var page in _document.Pages.Cast<Page>().Where(page => page != null))
            {
                if (index > 1)
                {
                    page.Elements.Add(new Line(100, 790, 520, 790, 0.5f, RgbColor.Gray));
                    page.Elements.Add(new Image(ImageData.GetImage(_companyLogoFooterImage), LeftMargin + 85f, 781, 0.24f) { Align = Align.Right, VAlign = VAlign.Top });
                }
                else
                {
                    page.Elements.Add(new Line(LeftMargin, 790, 520, 790, 0.5f, RgbColor.Gray));
                }

                page.Elements.Add(new Label(string.Format("{0} | {1}", index, _document.Pages.Count), 500, 785, 50, 10, CalibriRegular, 10, TextAlign.Right, RgbColor.DimGray));

                index++;
            }

            // Create a memory stream object to save to
            byte[] buffer = _document.Draw();

            return buffer;
        }

        private void AddSources(ProductInvolvementDataDto caDto)
        {
            const float titleAreaHeight = 30f;

            var page = AddNewPage();
            _sourcesPages.Add(page);

            float x = LeftMargin;
            float y = 0;

            page.Elements.Add(new Label("Sources", x + 3f, y + 5f, 500f, 15f, CalibriBold, 12, TextAlign.Left, new RgbColor(95, 96, 98)));
            page.Elements.Add(new Line(x, y + 22f, 545f, y + 22f, 3, RgbColor.DimGray));
            y += 25;

            StringBuilder strBldSourceText = new StringBuilder();
            StringBuilder strBldSourceIndNrs = new StringBuilder();

            List<PIDataSource> allSources = new List<PIDataSource>();

            foreach (var indicator in caDto.Indicators)
            {
                foreach (var iass in indicator.IndicatorAssessments)
                {
                    allSources.AddRange(iass.Sources);
                }
            }

            var sourcesByType = allSources.GroupBy(p => p.Type, (key, g) => new { Type = key, Sources = g.ToList() });

            foreach (var group in sourcesByType.Where(p=>p.Type != PIDataSourceType.None))
            {
                int i = 1;
                foreach (PIDataSource source in group.Sources)
                {
                    strBldSourceIndNrs.Append("<p><font color='#5b6065'>");
                    strBldSourceIndNrs.Append(source.Type.ToString() + "." + i);
                    strBldSourceIndNrs.Append("</font></p>");

                    strBldSourceText.Append("<p><font color='#5b6065'>");
                    strBldSourceText.Append(source.Name);
                    strBldSourceText.Append("</font></p>");

                    var formTxtAreaSourceIndNrs = new FormattedTextArea(strBldSourceIndNrs.ToString(), x + 3f, y + 5f, 40, 10, CalibriFontFamily, 10, false);
                    var reqHeightSourceIndNrs = formTxtAreaSourceIndNrs.GetRequiredHeight();

                    var formTxtAreaSourceText = new FormattedTextArea(strBldSourceText.ToString(), x + 25f, y + 5f, 300, 10, CalibriFontFamily, 10, false);
                    var reqHeightSourceText = formTxtAreaSourceText.GetRequiredHeight();

                    var reqHeight = Math.Max(reqHeightSourceIndNrs, reqHeightSourceText);

                    if (y + reqHeight > PageHeight)
                    {
                        page = AddNewPage();
                        _sourcesPages.Add(page);
                        y = 0;
                    }

                    formTxtAreaSourceIndNrs = new FormattedTextArea(strBldSourceIndNrs.ToString(), x + 3f, y + 5f, 40, reqHeight, CalibriFontFamily, 10, false);
                    formTxtAreaSourceIndNrs.Style.Paragraph.Align = TextAlign.Left;
                    page.Elements.Add(formTxtAreaSourceIndNrs);

                    formTxtAreaSourceText = new FormattedTextArea(strBldSourceText.ToString(), x + 25f, y + 5f, 300, reqHeight, CalibriFontFamily, 10, false);
                    formTxtAreaSourceText.Style.Paragraph.Align = TextAlign.Left;
                    page.Elements.Add(formTxtAreaSourceText);

                    y += reqHeight + 1f;

                    strBldSourceText.Clear();
                    strBldSourceIndNrs.Clear();

                    i++;
                }
            }

            //Add Disclaimer
            var copyright1 = $"<p><b>Copyright © {DateTime.UtcNow.Year} Sustainalytics. All rights reserved.</b></p>";

            var copyrightText = new StringBuilder();
            copyrightText.Append(copyright1);
            copyrightText.Append("<p>The ownership and all intellectual property rights to this publication/report and the information contained herein are vested exclusively in Sustainalytics and/or its suppliers. Unless otherwise expressly agreed in writing between you and Sustainalytics, you will not be permitted to use this information otherwise than for internal use, nor will you be permitted to reproduce, disseminate, comingle, create derivative works, furnish in any manner, make available to third parties or publish this publication/report, parts hereof or the information contained herein in any form or in any manner, be it electronically, mechanically, through photocopies, recordings.  The information on which this publication/report is based on reflects the situation as on the date of its elaboration. Such information has – fully or partially – been derived from third parties and is therefore subject to continuous modification. " +
                "THE INFORMATION HEREIN IS PROVIDED SOLELY FOR INFORMATIONAL PURPOSES AND THEREFORE ARE NOT AN OFFER TO BUY OR SELL A SECURITY. NEITHER SUSTAINALYTICS NOR ALL ITS THIRD-PARTY SUPPLIERS PROVIDE INVESTMENT ADVICE (AS DEFINED IN THE APPLICABLE JURISDICTION) OR ANY OTHER FORM OF (FINANCIAL) ADVICE AND NOTHING WITHIN THIS PUBLICATION/REPORT CONSTITUTES SUCH ADVICE. SUSTAINALYTICS OBSERVES THE GREATEST POSSIBLE CARE IN USING INFORMATION, HOWEVER THE INFORMATION IS PROVIDED “AS IS” AND NEITHER SUSTAINALYTICS NOR ITS SUPPLIERS ACCEPT ANY LIABILITY FOR DAMAGE ARISING FROM THE USE OF THIS PUBLICATION/REPORT OR INFORMATION CONTAINED HEREIN IN ANY MANNER WHATSOEVER. MOREOVER, SUSTAINALYTICS AND ALL ITS THIRD-PARTY SUPPLIERS DISCLAIM ANY AND ALL WARRANTIES AND REPRESENTATIONS, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OF MERCHANTIBILITY, COMPLETENESS, ACCURACY OR FITNESS FOR A PARTICULAR PURPOSE.</p>");
            copyrightText.Append("<br>");
            copyrightText.Append("<p>This publication/report may contain proprietary information from third parties (Third Party Data) and here you can find additional terms and conditions imposed by the following Third Party Data providers regarding the use of their data:</p>");
            copyrightText.Append("<br>");
            copyrightText.Append("<a href=\"http://www.sustainalytics.com/legal-disclaimers\"><font color=\"F0AB00\"><b>http://www.sustainalytics.com/legal-disclaimers</b></font></a>");
            copyrightText.Append("<p> </p>");

            var disclaimerSourceText = new FormattedTextArea(copyrightText.ToString(), x + 3f, y + 5f, 500, 10, CalibriFontFamily, 10, false);
            var disclaimerHeight = disclaimerSourceText.GetRequiredHeight();

            if (y + titleAreaHeight + disclaimerHeight > PageHeight)
            {
                //new page
                page = AddNewPage();
                _sourcesPages.Add(page);
                y = 0;
            }
            else
            {
                //footer
                y = PageHeight - titleAreaHeight - disclaimerHeight;
            }

            page.Elements.Add(new Label("Disclaimer", x + 3f, y +5f, 500f, 15f, CambriaBold, 14f));
            page.Elements.Add(new Line(x, y + 22f, 545f, y + 22f, 2f, RgbColor.Black));
            y += titleAreaHeight;

            disclaimerSourceText = new FormattedTextArea(copyrightText.ToString(), x + 3f, y + 5f, 500, disclaimerHeight, CalibriFontFamily, 10, false);
            disclaimerSourceText.Style.Paragraph.Align = TextAlign.Left;
            page.Elements.Add(disclaimerSourceText);
        }

        private Page AddNewPage()
        {
            var page = new Page(PageSize.A4, PageOrientation.Portrait, 20);
            if (ShowLayoutGrid)
                page.Elements.Add(new LayoutGrid { ShowTitle = false });

            return page;
        }

        private void AddProducts(ProductInvolvementDataDto ca, IEnumerable<ProductInvolvementFramework> piIndicators, List<string> userClaimedIndicators)
        {
            var page = AddNewPage();
            _productsPages.Add(page);

            var strBld = new StringBuilder();
            float x = LeftMargin;
            float y = 0;

            AddHeader(ca.CompanyName, page.Elements, LeftMargin, 30);
            AddCompanyInfo(ca, page.Elements, LeftMargin, 90);
            
            y += 140;

            page.Elements.Add(new Rectangle(x, y, 535, 20, _barBackgroundColor, _barBackgroundColor));
            page.Elements.Add(new FormattedTextArea("<p><font color='FFFFFF'><b>Product Involvement</b></font></p>", x + 5f, y + 3f, 500, 15, CalibriFontFamily, 14, false));

            y += 40;

            List<string> enabledPIIndicatorsSymbols = new List<string>();

            int offsetx = 60;
            int offsety = 85;

            int step = 0;
            foreach (ProductInvolvementFramework ind in piIndicators)
            {
                var crUserInd = ca.Indicators.Where(o => o.Symbol == ind.Symbol).SingleOrDefault();
                var indicatorNameLabel = new Label(ind.Name, x + 3f + (step * offsetx), y + 43f, 50f, 30f, CalibriBold, 7.3f, TextAlign.Center, new RgbColor(158, 160, 162));

                if (crUserInd != null && userClaimedIndicators.Contains(crUserInd.Symbol))
                {
                    page.Elements.Add(new Image(GetIndicatorImage(crUserInd.Symbol, true), x + 10f + (step * offsetx), y, 0.4f));
                    indicatorNameLabel = new Label(ind.Name, x + 3f + (step * offsetx), y + 43f, 50f, 30f, CalibriBold, 7.3f, TextAlign.Center, RgbColor.Black);
                    enabledPIIndicatorsSymbols.Add(crUserInd.Symbol);
                }
                else
                {
                    page.Elements.Add(new Image(GetIndicatorImage(ind.Symbol, false), x + 10f + (step * offsetx), y, 0.4f));
                }

                page.Elements.Add(indicatorNameLabel);

                step++;

                //max 9 indicators per row
                if (step % 9 == 0)
                {
                    y += offsety;
                    step = 0;
                }
            }

            // Must be commented for testing with fake data!
            if (!enabledPIIndicatorsSymbols.Any())
            {
                y += 80;
                page.Elements.Add(new Line(x, y, 540, y, 1f, RgbColor.Black));

                y += 5;

                strBld.Append("<p>No evidence has been found of significant involvement in any of the above mentioned items.</p>");
                
                var formTxtArea = new FormattedTextArea(strBld.ToString(), x + 5f, y + 5f, 550, 9, CalibriFontFamily, 9, false);
                page.Elements.Add(formTxtArea);
                strBld.Clear();

                return;
            }
            // -------------------------------------------

            y += 20;

            // Create an FormattedTextAreaStyle and set the initial properties for the FormattedTextArea
            var style = new FormattedTextAreaStyle(CalibriFontFamily, 9, false)
            {
                Paragraph = { Align = TextAlign.Justify, Indent = 0, SpacingAfter = 0, AllowOrphanLines = false }
            };

            var indNumberHeight = 40f;
            var imgHeight = 50;

            foreach (var symbol in enabledPIIndicatorsSymbols)
            {

                var userPIIndicator = ca.Indicators.SingleOrDefault(o => o.Symbol == symbol);

                var indicatorNameLabel = new Label(userPIIndicator.Name, x, y, 60f, indNumberHeight, CalibriBold, 9.8f, TextAlign.Left, RgbColor.Black);
                page.Elements.Add(indicatorNameLabel);
                page.Elements.Add(new Image(GetIndicatorImage(userPIIndicator.Symbol, true), x + 100f, y, 0.5f));

                foreach (PIIndicatorAssessmentDto pia in userPIIndicator.IndicatorAssessments)
                {
                    strBld.Append("<p><b>");
                    strBld.Append(pia.CategorySymbolDescription);
                    strBld.Append("</b></p>");

                    strBld.Append("<p><b>");
                    if (pia.OwnershipPercent.NearlyEq(0))
                    {
                        strBld.Append("Range: " + pia.LevelOfInvolvement.Text);
                    }
                    else if (!pia.OwnershipPercent.NearlyEq(-1))
                    {
                        strBld.Append("Ownership: " + pia.OwnershipPercent * 100 + "%");
                    }
                    strBld.Append("</b></p>");

                    strBld.Append("<p></p>");
                    strBld.Append("<p>");
                    strBld.Append(pia.StandardAnalysis);
                    strBld.Append("</p>");

                    var formTxtArea = new FormattedTextArea(strBld.ToString(), x + 165f, y, 360, 10, style);
                    var reqHeight = formTxtArea.GetRequiredHeight();

                    if (y + reqHeight > 770)
                    {
                        page = AddNewPage();
                        _productsPages.Add(page);
                        y = 0;
                    }

                    y += 3;

                    if (formTxtArea.HasOverflowText())
                    {
                        page.Elements.Add(new FormattedTextArea(strBld.ToString(), x + 165f, y + 5f, 360, reqHeight, style));
                    }
                    else
                    {
                        page.Elements.Add(formTxtArea);
                    }

                    if ((y + imgHeight) > (y + reqHeight))
                    {
                        y += 65;
                    }
                    else
                    {
                        y += (reqHeight + 15);
                    }

                    if (y + reqHeight > 770)
                    {
                        page = AddNewPage();
                        _productsPages.Add(page);
                        y = 0;
                    }

                    strBld.Clear();
                }
            }
        }

        private void AddHeader(string companyName, Group pageElements, float x, float y)
        {
            pageElements.Add(new Label(companyName, x, y - 12, 340f, 25f, CalibriBold, 24, TextAlign.Left, RgbColor.Black));
            pageElements.Add(new Image(ImageData.GetImage(_companyLogoHeaderImage), x + 540f, y - 20, 0.25f) { Align = Align.Right, VAlign = VAlign.Top });
            pageElements.Add(new Label("PRODUCT INVOLVEMENT REPORT", x + 390, y + 14f, 150, 30f, CalibriBold, 12, TextAlign.Right, new RgbColor(188, 190, 192)));
            pageElements.Add(new Line(x, y + 50, 550, y + 50, 3, RgbColor.DimGray));
        }

        private void AddCompanyInfo(ProductInvolvementDataDto caDto, Group pageElements, float x, float y)
        {
            //line1
            pageElements.Add(new Label("Industry:", x, y, 50f, 10f, CalibriLight, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            pageElements.Add(new Label(caDto.PeerGroupName, x + 50, y, 140f, 10f, CalibriBold, 10, TextAlign.Left, new RgbColor(91, 96, 101)));

            pageElements.Add(new Label("Marketcap:", x + 200, y, 50f, 10f, CalibriLight, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            pageElements.Add(new Label(caDto.MarketCap.ToString(), x + 250, y, 100f, 10f, CalibriBold, 10, TextAlign.Left, new RgbColor(91, 96, 101)));


            //calculate last edited date
            List<PIIndicatorAssessmentDto> indAss = new List<PIIndicatorAssessmentDto>();
            foreach (ProductInvolvementCompanyIndicatorDto ind in caDto.Indicators)
            {
                indAss.AddRange(ind.IndicatorAssessments);
            }
            DateTime lastEdited = indAss.Max(o => o.LastEdited);

            pageElements.Add(new Label("Updated:", x + 400, y, 50f, 10f, CalibriLight, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            if (lastEdited != null)
            {
                pageElements.Add(new Label(lastEdited.ToString("dd MMMM yyyy"), x + 450, y, 100f, 10f, CalibriBold, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            }

            //line2
            pageElements.Add(new Label("Domicile:", x, y + 20f, 50f, 10f, CalibriLight, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            pageElements.Add(new Label(caDto.Country, x + 50, y + 20f, 100f, 10f, CalibriBold, 10, TextAlign.Left, new RgbColor(91, 96, 101)));

            pageElements.Add(new Label("Ticker:", x + 200, y + 20f, 50f, 10f, CalibriLight, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            pageElements.Add(new Label(caDto.Ticker, x + 250, y + 20f, 100f, 10f, CalibriBold, 10, TextAlign.Left, new RgbColor(91, 96, 101)));

            pageElements.Add(new Label("Employees:", x + 400, y + 20f, 50f, 10f, CalibriLight, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
            pageElements.Add(new Label(caDto.Employees.ToString(), x + 450, y + 20f, 100f, 10f, CalibriBold, 10, TextAlign.Left, new RgbColor(91, 96, 101)));
        }

        
        static void LoadStaticImages()
        {
            _companyLogoHeaderImage = Sustainalytics.ReportGeneratorFramework.Utils.GetJpgImage(AssemblyDirectory + @"\Images\sustainalytics_logo_header.jpg");
            _companyLogoFooterImage = Sustainalytics.ReportGeneratorFramework.Utils.GetJpgImage(AssemblyDirectory + @"\Images\sustainalytics_logo_footer.jpg");
        }

        public ImageData GetIndicatorImage(string indicatorSymbol, bool enabled)
        {
            string strOnOff = enabled ? "on" : "off";
            byte[] imgBytes = Sustainalytics.ReportGeneratorFramework.Utils.GetPngFromSvgImage(string.Format("icn-pi-{0}_{1}.svg", indicatorSymbol, strOnOff));
            return ImageData.GetImage(imgBytes);
        }

    }
}

